﻿using System;
using UnityEngine;

namespace Framework.Scripts.MapData
{
    public class XYMap<T>
    {
        private readonly T[][] map;
        private readonly int x;
        private readonly int y;

        public XYMap(int x, int y)
        {
            this.x = x;
            this.y = y;
            map = new T[x][];
        }
        // public T this[int x, int y]
        // {
        //     get { return Get(x, y); }
        //     set
        //     {
        //         Set(x, y, value);
        //     }
        // }
        // public T this[Vector2Int p]
        // {
        //     get { return Get(p); }
        //     set
        //     {
        //         Set(p, value);
        //     }
        // }

        private T Get(int x, int y)
        {
            var v = map[x];
            if (v == null)
            {
                v = new T[this.y];
                map[x] = v;
            }

            return v[y];
        }

        public T Get(Vector2Int p, bool center = true)
        {
            if (center) return Get(GetX(p.x), GetY(p.y));
            return Get(p.x, p.y);
        }

        private void Set(int x, int y, T i)
        {
            var v = map[x];
            if (v == null)
            {
                v = new T[this.y];
                map[x] = v;
            }

            v[y] = i;
        }

        public void Set(Vector2Int p, T i, bool center = true)
        {
            if (center)
                Set(GetX(p.x), GetY(p.y), i);
            else
                Set(p.x, p.y, i);
        }

        private bool Has(int x, int y)
        {
            var v = map[x];
            if (v == null) return false;
            return v[y] != null;
        }

        public bool Has(Vector2Int p, bool center = true)
        {
            if (center)
                return Has(GetX(p.x), GetY(p.y));
            return Has(p.x, p.y);
        }

        private bool In(int x, int y)
        {
            if (x >= 0 && x < this.x && y >= 0 && y < this.y) return true;
            return false;
        }

        public bool In(Vector2Int p, bool center = true)
        {
            if (center)
                return In(GetX(p.x), GetY(p.y));
            return In(p.x, p.y);
        }

        private int GetX(int f)
        {
            return f + x / 2;
        }

        private int GetY(int f)
        {
            return f + y / 2;
        }

        private int GetRealX(int f)
        {
            return f - x / 2;
        }

        private int GetRealY(int f)
        {
            return f - y / 2;
        }

        private int GetX(float f)
        {
            return (int)Math.Round(f + x / 2, MidpointRounding.AwayFromZero);
        }

        private int GetY(float f)
        {
            return (int)Math.Round(f + y / 2, MidpointRounding.AwayFromZero);
        }

        private Vector2Int GetKey(int x, int y, bool center = true)
        {
            if (center)
                return new Vector2Int(GetRealX(x), GetRealY(y));
            return new Vector2Int(x, y);
        }

        public void ForEach(Action<Vector2Int, T> cb, bool center = true)
        {
            for (var i = 0; i < x; i++)
            for (var j = 0; j < y; j++)
                if (Has(i, j))
                    cb(GetKey(i, j, center), Get(i, j));
        }
    }
}